package com.atguigu.gmall.seckill.service.impl;

import com.atguigu.gmall.common.constant.RedisConstant;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.JsonUtils;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.seckill.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.seckill.service.SeckillCacheService;
import com.atguigu.gmall.seckill.service.SeckillGoodsService;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


@Service
public class SeckillCacheServiceImpl implements SeckillCacheService {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SeckillGoodsService seckillGoodsService;

    @Resource
    SeckillGoodsMapper seckillGoodsMapper;

    //本地缓存
    Map<Long,SeckillGoods> localGoodsCache = new ConcurrentHashMap<>();

    @Override
    public void cacheSaveGoods(List<SeckillGoods> goods) {
        //2、缓存预热
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstant.SECKILL_GOOD + DateUtil.formatDate(new Date()));

        //1、查预热缓存
//        List<SeckillGoods> collect = goods.stream()
//                .sorted((o1, o2) -> o1.getStartTime().compareTo(o2.getStartTime()))
//                .collect(Collectors.toList());
        //3、保存数据; hash的操作是幂等的； map.put(11,"aaa")
        //select、update、delete； 幂等操作  2^N
        //insert： 非幂等的操作
        for (SeckillGoods good : goods) {
            String jsonStr = JsonUtils.parseObjectToJsonStr(good);
            hashOps.put(good.getSkuId().toString(),jsonStr);
        }
    }

    @Override
    public List<SeckillGoods> getTodaySeckillGoodsFromCache(String format) {
        //1、本地缓存查询数据
        List<SeckillGoods> localCacheData = localGoodsCache.values()
                .stream().sorted((o1, o2) -> o1.getStartTime().compareTo(o2.getStartTime()))
                .collect(Collectors.toList());
        if(localCacheData!=null && localCacheData.size()>0){
            return localCacheData;
        }



        //防止我们微服务炸了

        //2、本地缓存没有就查redis
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(RedisConstant.SECKILL_GOOD + format);

        //3、第一次查预热缓存
        List<SeckillGoods> collect = hashOps.values()
                .stream()
                .map(jsonStr -> JsonUtils.parseJsonStrToObject(jsonStr, new TypeReference<SeckillGoods>() {
                }))
                .sorted((o1, o2) -> o1.getStartTime().compareTo(o2.getStartTime()))
                .collect(Collectors.toList());


        //2、防止提前刷新秒杀页，要当天的数据没有。
        //1）、一次性上架两天的东西
        //2）、缓存没命中就查一次数据库即可
        if(collect == null || collect.size() <= 0){
            //查出数据，如果允许无秒杀商品上架，用解决缓存穿透，缓存null数据来防止这个问题
            List<SeckillGoods> goods = seckillGoodsMapper.selectTodaySeckillGoods(format);
            cacheSaveGoods(goods);
            syncLocalCache(goods);
            return goods.stream().sorted((o1, o2) -> o1.getStartTime().compareTo(o2.getStartTime())).collect(Collectors.toList());
        }else {
            //本地同步一份
            syncLocalCache(collect);
            return collect;
        }

    }

    @Override
    public SeckillGoods getGoodsDetail(Long skuId) {
        //直接从本地拿到这个商品数据
        SeckillGoods goods = localGoodsCache.get(skuId);
        if(goods == null){
            //内存同步一下redis的数据
            getTodaySeckillGoodsFromCache(DateUtil.formatDate(new Date()));
            //同步数据以后再拿
            goods = localGoodsCache.get(skuId);
        }
        //本地的
        return goods;
    }

    @Override
    public void syncLocalCache(List<SeckillGoods> goods) {
        for (SeckillGoods good : goods) {
            localGoodsCache.put(good.getSkuId(),good);
        }
    }
}
